package epam.com.rd.MyCollections;

import java.util.Arrays;
import java.util.Iterator;
import java.util.Objects;

public class MyHashMap implements MyMap {

	private static final int DEFAULT_INITIAL_CAPACITY = 16;

	private static final int MAXIMUM_CAPACITY = 1073741824;

	private static final float DEFAULT_LOAD_FACTOR = 0.75f;

	private int size;

	private final float loadFactor;

	private int threshold;

	private static final SimpleEntry[] EMPTY_TABLE = {};

	private SimpleEntry[] table = EMPTY_TABLE;

	private int hashSeed = 31;

	static class SimpleEntry implements MyMap.Entry {
		int hash;
		final Object key;
		Object value;
		SimpleEntry next;

		SimpleEntry(int h, Object k, Object v, SimpleEntry nextEntry) {
			value = v;
			next = nextEntry;
			key = k;
			hash = h;
		}

		public Object getKey() {
			// TODO Auto-generated method stub
			return key;
		}

		public Object getValue() {
			// TODO Auto-generated method stub
			return value;
		}

		public Object setValue(Object v) {
			Object returnValue = value;
			value = v;
			return returnValue;
		}

		public final int hashCode() {
			return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
		}

	}

	public MyHashMap(int initialCapacity, float loadFactor) {
		if (initialCapacity < 0)
			throw new IllegalArgumentException("Illegal initial capacity: "
					+ initialCapacity);
		if (initialCapacity > MAXIMUM_CAPACITY)
			initialCapacity = MAXIMUM_CAPACITY;
		if (loadFactor <= 0 || Float.isNaN(loadFactor))
			throw new IllegalArgumentException("Illegal load factor: "
					+ loadFactor);

		this.loadFactor = loadFactor;
		threshold = initialCapacity;
	}

	public MyHashMap(int initialCapacity) {
		this(initialCapacity, DEFAULT_LOAD_FACTOR);
	}

	public MyHashMap() {
		this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
	}

	public void clear() {
		Arrays.fill(table, null);
		size = 0;
	}

	public int size() {
		return size;
	}

	public boolean isEmpty() {
		if (size == 0)
			return true;
		else {
			return false;
		}
	}

	private SimpleEntry getEntry(Object key) {
		if (size == 0) {
			return null;
		}
		int hash = (key == null) ? 0 : hash(key);
		for (SimpleEntry e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
			Object k;
			if (e.hash == hash
					&& ((k = e.key) == key || (key != null && key.equals(k))))
				return e;
		}
		return null;
	}

	private static int indexFor(int h, int length) {
		return h & (length - 1);
	}

	private int hash(Object k) {
		int h = hashSeed;
		h ^= k.hashCode();
		h ^= (h >>> 20) ^ (h >>> 12);
		return h ^ (h >>> 7) ^ (h >>> 4);
	}

	public boolean containsKey(Object key) {
		return getEntry(key) != null;
	}

	public boolean containsValue(Object value) {
		if (value == null) {
			SimpleEntry[] tab = table;
			for (int i = 0; i < tab.length; i++)
				for (SimpleEntry e = tab[i]; e != null; e = e.next)
					if (e.value == null)
						return true;
			return false;
		}
		SimpleEntry[] tab = table;
		for (int i = 0; i < tab.length; i++)
			for (SimpleEntry e = tab[i]; e != null; e = e.next)
				if (value.equals(e.value))
					return true;
		return false;
	}

	public Object get(Object key) {
		if (key == null) {
			if (size == 0) {
				return null;
			}
			for (SimpleEntry e = table[0]; e != null; e = e.next) {
				if (e.key == null)
					return e.value;
			}
			return null;
		}

		SimpleEntry entry = getEntry(key);
		return null == entry ? null : entry.getValue();
	}

	public Object put(Object key, Object value) {
		if (table == EMPTY_TABLE) {
			int capacity = threshold;

			threshold = (int) Math.min(capacity * loadFactor,
					MAXIMUM_CAPACITY + 1);
			table = new SimpleEntry[capacity];

			hashSeed = sun.misc.Hashing.randomHashSeed(this);
		}

		if (key == null) {
			for (SimpleEntry e = table[0]; e != null; e = e.next) {
				if (e.key == null) {
					Object oldValue = e.value;
					e.value = value;
					return oldValue;
				}
			}
			addEntry(0, null, value, 0);
			return null;
		}

		int hash = hash(key);
		int i = indexFor(hash, table.length);
		for (SimpleEntry e = table[i]; e != null; e = e.next) {
			Object k;
			if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
				Object oldValue = e.value;
				e.value = value;
				return oldValue;
			}
		}
		addEntry(hash, key, value, i);
		return null;
	}

	private void addEntry(int hash, Object key, Object value, int bucketIndex) {
		if ((size >= threshold) && (null != table[bucketIndex])) {

			int newCapacity = 2 * table.length;
			SimpleEntry[] oldTable = table;
			int oldCapacity = oldTable.length;
			SimpleEntry[] newTable = new SimpleEntry[newCapacity];

			int newCap = newTable.length;
			for (SimpleEntry e : table) {
				while (null != e) {
					SimpleEntry next = e.next;

					e.hash = null == e.key ? 0 : hash(e.key);

					int i = indexFor(e.hash, newCap);
					e.next = newTable[i];
					newTable[i] = e;
					e = next;
				}
			}
			table = newTable;
			threshold = (int) Math.min(newCapacity * loadFactor,
					MAXIMUM_CAPACITY + 1);
			hash = (null != key) ? hash(key) : 0;
			bucketIndex = indexFor(hash, table.length);
		}

		SimpleEntry e = table[bucketIndex];
		table[bucketIndex] = new SimpleEntry(hash, key, value, e);
		size++;

	}

	public Object remove(Object key) {
		SimpleEntry se;

		if (size == 0) {
			return null;
		}
		int hash = (key == null) ? 0 : hash(key);
		int i = indexFor(hash, table.length);
		SimpleEntry prev = table[i];
		SimpleEntry e = prev;
		while (e != null) {
			SimpleEntry next = e.next;
			Object k;
			if (e.hash == hash
					&& ((k = e.key) == key || (key != null && key.equals(k)))) {
				size--;
				if (prev == e)
					table[i] = next;
				else
					prev.next = next;
				se = e;
			}
			prev = e;
			e = next;
		}
		se = e;

		return (se == null ? null : se.value);
	}

	public Iterator entryIterator() {
		// TODO Auto-generated method stub
		return null;
	}

}
